// hooks.go

package main

import (
	"bytes"
	"fmt"

	"time"

	"gorm.io/gorm"

	mqtt "github.com/mochi-mqtt/server/v2"
	"github.com/mochi-mqtt/server/v2/packets"
)

// Options contains configuration settings for the hook.
type ExampleHookOptions struct {
	Server *mqtt.Server
}

type ExampleHook struct {
	mqtt.HookBase
	config *ExampleHookOptions
	DB     *gorm.DB
}

func (h *ExampleHook) ID() string {
	return "events-example"
}

func (h *ExampleHook) Provides(b byte) bool {
	return bytes.Contains([]byte{
		mqtt.OnConnect,
		mqtt.OnDisconnect,
		mqtt.OnSubscribed,
		mqtt.OnUnsubscribed,
		mqtt.OnPublished,
		mqtt.OnPublish,
	}, []byte{b})
}

func (h *ExampleHook) Init(config any) error {
	h.Log.Info("initialised")
	if _, ok := config.(*ExampleHookOptions); !ok && config != nil {
		return mqtt.ErrInvalidConfigType
	}

	h.config = config.(*ExampleHookOptions)
	if h.config.Server == nil {
		return mqtt.ErrInvalidConfigType
	}
	return nil
}

// subscribeCallback handles messages for subscribed topics
func (h *ExampleHook) subscribeCallback(cl *mqtt.Client, sub packets.Subscription, pk packets.Packet) {
	h.Log.Info("hook subscribed message", "client", cl.ID, "topic", pk.TopicName)
}

func (h *ExampleHook) OnConnect(cl *mqtt.Client, pk packets.Packet) error {
	clientID := cl.ID
	connectedAt := time.Now()
	fmt.Printf("MochiMqtt ConnectHook ClientID: %s, time: %s\n", clientID, connectedAt.String())

	// 使用FirstOrCreate查找或创建ConnectionLog
	connectionLog := createNewLog(clientID, connectedAt)
	if err := h.DB.Where("client_id = ?", clientID).FirstOrCreate(&connectionLog).Error; err != nil {
		fmt.Println("连接数据库失败:", err)
		return err
	}

	// 如果记录已存在，更新连接时间
	if connectionLog.ID != 0 { // 已存在记录，更新日志
		updateConnectionLog(&connectionLog, connectedAt)
		if err := h.DB.Save(&connectionLog).Error; err != nil {
			fmt.Println("更新连接日志失败:", err)
			return err
		}
	}

	return nil
}

func (h *ExampleHook) OnDisconnect(cl *mqtt.Client, err error, expire bool) {
	clientID := cl.ID
	disconnectedAt := time.Now()
	fmt.Printf("MochiMqtt DisconnectHook ClientID: %s, time: %s\n", clientID, disconnectedAt.String())

	var oldLog ConnectionLog
	if err := h.DB.Where("client_id = ?", clientID).First(&oldLog).Error; err != nil {
		fmt.Println("设备未记录")
		return
	}

	updateDisconnectionLog(&oldLog, disconnectedAt)
	if err := h.DB.Save(&oldLog).Error; err != nil {
		fmt.Println("更新断连日志失败:", err)
		return
	}

	fmt.Println("更新设备断连时间成功")

	// mochi实现了disconnect时对所有的subscribe调用unsubscribe
	// if err := h.DB.Where("client_id = ?", clientID).Delete(&SubscribeList{}).Error; err != nil {
	// 	fmt.Printf("删除订阅失败: %v\n", err)
	// 	return
	// }

	// fmt.Printf("clientID: %s 的所有订阅已删除\n", clientID)
}

func (h *ExampleHook) OnSubscribed(cl *mqtt.Client, pk packets.Packet, reasonCodes []byte) {
	h.Log.Info(fmt.Sprintf("subscribed qos=%v", reasonCodes), "client", cl.ID, "filters", pk.Filters)

	for _, filter := range pk.Filters {
		topic := filter.Filter
		var newSubscribe SubscribeList
		newSubscribe.ClientID = cl.ID
		newSubscribe.Topic = topic
		newSubscribe.LastTime = time.Now()

		// 使用FirstOrCreate方法，如果找不到记录则创建
		if err := h.DB.Where("client_id = ? AND topic = ?", cl.ID, topic).FirstOrCreate(&newSubscribe).Error; err != nil {
			fmt.Println("Err: Record Subscribe Topic ", err)
			return
		}

		// 打印订阅信息
		fmt.Printf("client:%s 订阅topic:%s\n", cl.ID, topic)
	}
}

func (h *ExampleHook) OnUnsubscribed(cl *mqtt.Client, pk packets.Packet) {
	h.Log.Info("client", cl.ID, "filters", pk.Filters)

	for _, filter := range pk.Filters {
		topic := filter.Filter
		var newSubscribe SubscribeList
		newSubscribe.ClientID = cl.ID
		newSubscribe.Topic = topic
		if err := h.DB.Where("client_id = ? AND topic = ?", cl.ID, topic).First(&newSubscribe).Error; err != nil {
			fmt.Println("Err:", err)
		} else {
			h.DB.Delete(&newSubscribe)
			fmt.Println("client unsubscribe topic ", topic)
		}
	}
}

func (h *ExampleHook) OnPublish(cl *mqtt.Client, pk packets.Packet) (packets.Packet, error) {
	h.Log.Info("received from client", "client", cl.ID, "payload", string(pk.Payload))

	pkx := pk
	if string(pk.Payload) == "hello" {
		pkx.Payload = []byte("hello world")
		h.Log.Info("received modified packet from client", "client", cl.ID, "payload", string(pkx.Payload))
	}

	return pkx, nil
}

func (h *ExampleHook) OnPublished(cl *mqtt.Client, pk packets.Packet) {
	h.Log.Info("published to client", "client", cl.ID, "payload", string(pk.Payload))
}
